package weiyao.xinxidasai.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import weiyao.xinxidasai.Utils.Encryption.Chaos.CEOEncryptor;
import weiyao.xinxidasai.Utils.Encryption.Chaos.ChaosEncryptor;
import weiyao.xinxidasai.Utils.Encryption.Chaos.tokenCEOEncryptor;
import weiyao.xinxidasai.Utils.Encryption.Chaos.tokenChaosEncryptor;
import weiyao.xinxidasai.Utils.FileTypes;
import weiyao.xinxidasai.Utils.ThreadLocal.UserHolder;
import weiyao.xinxidasai.Utils.OSS.OSSDownloader;
import weiyao.xinxidasai.mapper.FilesMapper;
import weiyao.xinxidasai.mapper.VersionMapper;
import weiyao.xinxidasai.pojo.FFiles;
import weiyao.xinxidasai.pojo.Version;
import weiyao.xinxidasai.service.DownloadService;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.concurrent.*;

import static weiyao.xinxidasai.Utils.Encryption.KeyIdGenerator.validateKeyId;

@Slf4j
@Service
public class DownloadServiceImpl implements DownloadService {
    private static final String TEMP_DIR = System.getProperty("java.io.tmpdir") + "/downloads/";
    private static final int CHUNK_SIZE = 10 * 1024 * 1024; // 10MB分片大小
    private static final int MAX_FILE_RETENTION_MINUTES = 30; // 临时文件保留时间
    private static final Logger logger = LoggerFactory.getLogger(DownloadServiceImpl.class);

    // 缓存解密后的文件（文件ID -> 解密文件）
    private final Map<Long, DecryptedFileHolder> decryptedFilesCache = new ConcurrentHashMap<>();
    private final ScheduledExecutorService cleanupExecutor = Executors.newSingleThreadScheduledExecutor();

    @Autowired
    private FilesMapper filesMapper;
    @Autowired
    private VersionMapper versionMapper;
    @Autowired
    private OSSDownloader ossDownloader;
    @Autowired
    private tokenCEOEncryptor tokenCEOEncryptor;
    @Autowired
    private tokenChaosEncryptor tokenChaosEncryptor;

    public DownloadServiceImpl() {
        // 启动定时清理任务
        cleanupExecutor.scheduleAtFixedRate(this::cleanupOldFiles, 10, 10, TimeUnit.MINUTES);
    }

    @Override
    public ResponseEntity<Resource> downloadFile(Long fileId) throws IOException {
        Long userId = UserHolder.getUser().getUserId();

        FFiles fileRecord = validateFileAccess(fileId, userId);
        if (fileRecord == null) {
            return ResponseEntity.badRequest().body(null);
        }
        File decryptedFile = getOrCreateDecryptedFile(fileRecord);
        return buildResponseEntity(fileRecord, decryptedFile);
    }

    @Override
    public FFiles initChunkDownload(Long fileId) {
        Long userId = UserHolder.getUser().getUserId();
        FFiles fileRecord = validateFileAccess(fileId, userId);
        System.out.println("fileRecord = " + fileRecord.getOssNo());
        if (fileRecord.getOssNo() == null) {
            return null;
        }
        fixFileNameExtension(fileRecord);
        return fileRecord;
    }

    @Override
    public ResponseEntity<Resource> downloadChunk(Long fileId, Integer chunkIndex, Long chunkSize) throws IOException {
        Long userId = UserHolder.getUser().getUserId();
        FFiles fileRecord = validateFileAccess(fileId, userId);

        File decryptedFile = getOrCreateDecryptedFile(fileRecord);
        return readFileChunk(decryptedFile, chunkIndex, chunkSize);
    }

    // ============== 核心解密逻辑 ==============

    /**
     * 获取或创建解密后的文件
     */
    private File getOrCreateDecryptedFile(FFiles fileRecord) throws IOException {
        // 检查缓存中是否已有解密文件
        DecryptedFileHolder holder = decryptedFilesCache.get(fileRecord.getFilesId());
        if (holder != null && holder.file.exists()) {
            holder.updateLastAccess();
            return holder.file;
        }

        // 创建临时目录
        Path dir = Paths.get(TEMP_DIR);
        if (!Files.exists(dir)) {
            Files.createDirectories(dir);
        }

        // 创建加密临时文件
        String encryptedPrefix = "encrypted-" + fileRecord.getFilesId() + "-";
        File encryptedTemp = File.createTempFile(encryptedPrefix, ".tmp", dir.toFile());
        // 获取版本信息
        Version version = versionMapper.getVersionByVersionId(fileRecord.getVersionId());
        Long versionId = version.getVersionId();
        boolean b = validateKeyId(versionId, UserHolder.getUser().getUserId(), version.getR(), version.getX0(), version.getIterations());
        if (!b) {
            throw new IOException("无效的文件版本或者密钥校验未通过");
        }
        try {
            // 从OSS下载加密文件
            if (!ossDownloader.downloadFile(fileRecord.getOssNo(), encryptedTemp)) {
                throw new IOException("文件下载失败");
            }

            // 创建解密后的文件（保留原始扩展名）
            String suffix = fileRecord.getFileType() != null && !fileRecord.getFileType().isEmpty()
                    ? "." + fileRecord.getFileType() : "";
            String decryptedPrefix = "decrypted-" + fileRecord.getFilesId() + "-";
            File decryptedFile = File.createTempFile(decryptedPrefix, suffix, dir.toFile());

            // 根据加密类型选择解密算法
            decryptFileBasedOnEncryptionType(encryptedTemp, decryptedFile, fileRecord, version);

            // 双重验证文件类型（后缀名 + 文件头）
            validateFileType(fileRecord, decryptedFile);

            // 验证解密文件大小
            validateDecryptedFileSize(decryptedFile, fileRecord);

            // 添加到缓存
            holder = new DecryptedFileHolder(decryptedFile);
            decryptedFilesCache.put(fileRecord.getFilesId(), holder);

            return decryptedFile;
        } finally {
            // 清理加密临时文件
            Files.deleteIfExists(encryptedTemp.toPath());
        }
    }

    /**
     * 双重验证文件类型（后缀名 + 文件头），以数据库记录为准
     */
    private void validateFileType(FFiles fileRecord, File decryptedFile) throws IOException {
        String dbFileType = fileRecord.getFileType();
        if (dbFileType == null || dbFileType.isEmpty()) {
            logger.warn("数据库文件类型为空，文件ID: {}", fileRecord.getFilesId());
            return;
        }

        // 1. 验证文件后缀名
        String fileName = decryptedFile.getName().toLowerCase();
        boolean hasValidSuffix = fileName.endsWith("." + dbFileType.toLowerCase());

        // 2. 使用文件头检测真实类型
        String detectedType = FileTypes.getType(decryptedFile);
        boolean hasValidHeader = !"-1".equals(detectedType) &&
                detectedType.equalsIgnoreCase(dbFileType);

        // 3. 记录验证结果
        if (!hasValidSuffix) {
            logger.warn("文件后缀名不匹配: 文件ID={}, 数据库类型={}, 文件后缀={}",
                    fileRecord.getFilesId(), dbFileType, fileName.substring(fileName.lastIndexOf('.') + 1));
        }

        if (!hasValidHeader) {
            logger.warn("文件头类型不匹配: 文件ID={}, 数据库类型={}, 检测类型={}",
                    fileRecord.getFilesId(), dbFileType, detectedType);
        }

        // 4. 如果文件头检测失败但后缀名正确，尝试修复文件类型（以数据库为准）
        if (!hasValidHeader && hasValidSuffix) {
            logger.info("文件头检测失败但后缀名匹配，以数据库类型为准: 文件ID={}, 类型={}",
                    fileRecord.getFilesId(), dbFileType);
            return;
        }

        // 5. 如果两种检测都不匹配，记录严重警告（但依然以数据库为准）
        if (!hasValidSuffix && !hasValidHeader) {
            logger.error("严重警告: 文件类型双重验证失败! 文件ID={}, 数据库类型={}, 检测类型={}, 文件后缀={}",
                    fileRecord.getFilesId(), dbFileType, detectedType,
                    fileName.substring(fileName.lastIndexOf('.') + 1));
        }
    }

    /**
     * 根据加密类型选择解密算法
     */
    private void decryptFileBasedOnEncryptionType(File encryptedFile, File decryptedFile,
                                                  FFiles fileRecord, Version version) throws IOException {
        String token = fileRecord.getFilesToken();
        String encryptionType = fileRecord.getEncryptionType(); // 从数据库获取加密类型

        if (token != null) {
            // Token加密文件
            if ("enhanced".equals(encryptionType)) {
                tokenCEOEncryptor.decryptFile(encryptedFile, decryptedFile, version, token);
                logger.debug("使用CEO+TOKEN解密文件: {}", fileRecord.getDisplayName());
            } else {
                tokenChaosEncryptor.decryptFile(encryptedFile, decryptedFile, version, token);
                logger.debug("使用LogisticMap+TOKEN解密文件: {}", fileRecord.getDisplayName());
            }
        } else {
            // 普通加密文件
            if ("enhanced".equals(encryptionType)) {
                CEOEncryptor.decryptFile(encryptedFile, decryptedFile, version);
                logger.debug("使用CEO解密文件: {}", fileRecord.getDisplayName());
            } else {
                ChaosEncryptor.decryptFile(encryptedFile, decryptedFile, version);
                logger.debug("使用LogisticMap解密文件: {}", fileRecord.getDisplayName());
            }
        }
    }

    // ============== 辅助方法 ==============

    /**
     * 验证解密后文件大小
     */
    private void validateDecryptedFileSize(File decryptedFile, FFiles fileRecord) throws IOException {
        if (decryptedFile.length() != fileRecord.getFileSize()) {
            throw new IOException("解密后文件大小不匹配: 预期=" + fileRecord.getFileSize() +
                    ", 实际=" + decryptedFile.length());
        }
    }

    /**
     * 修正文件名扩展名
     */
    private void fixFileNameExtension(FFiles fileRecord) {
        String displayName = fileRecord.getDisplayName();
        String fileType = fileRecord.getFileType();

        if (fileType != null && !fileType.isEmpty() && !displayName.contains(".")) {
            fileRecord.setDisplayName(displayName + "." + fileType);
        }
    }

    private FFiles validateFileAccess(Long fileId, Long userId) {
        FFiles fileRecord = filesMapper.getFileById(fileId);

        // 1. 优先检查文件是否存在
        if (fileRecord == null) {
            throw new RuntimeException("文件不存在");
        }

        // 2. 再检查OSS编号
        String ossNo = fileRecord.getOssNo();
        if (ossNo == null || ossNo.isEmpty()) {
            throw new RuntimeException("正在为您处理加密上传过程，请稍后再试");
        }

        // 3. 最后检查用户权限
        if (!fileRecord.getUserId().equals(userId)) {
            throw new RuntimeException("无访问权限");
        }

        return fileRecord;
    }

    /**
     * 读取文件分片
     */
    private ResponseEntity<Resource> readFileChunk(File file, Integer chunkIndex, Long chunkSize)
            throws IOException {

        long startByte = (long) chunkIndex * chunkSize;
        long endByte = Math.min(startByte + chunkSize - 1, file.length() - 1);
        long chunkLength = endByte - startByte + 1;

        try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
            raf.seek(startByte);
            byte[] buffer = new byte[(int) chunkLength];
            int bytesRead = raf.read(buffer);

            if (bytesRead != chunkLength) {
                throw new IOException("读取分片数据不完整: 预期=" + chunkLength +
                        ", 实际=" + bytesRead);
            }

            Resource resource = new ByteArrayResource(buffer);

            return ResponseEntity.ok()
                    .header("X-Chunk-Index", String.valueOf(chunkIndex))
                    .header("X-Chunk-Start", String.valueOf(startByte))
                    .header("X-Chunk-End", String.valueOf(endByte))
                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
                    .contentLength(chunkLength)
                    .body(resource);
        }
    }

    /**
     * 构建响应实体
     */
    private ResponseEntity<Resource> buildResponseEntity(FFiles file, File decryptedFile)
            throws IOException {

        String correctFileName = ensureFileExtension(file.getDisplayName(), file.getFileType());
        System.out.println("correctFileName = " + correctFileName);
        Resource resource = new FileSystemResource(decryptedFile) {
            @Override
            public String getFilename() {
                return correctFileName;
            }
        };

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment; filename=\"" + correctFileName + "\"")
                .contentType(MediaType.parseMediaType(file.getMimeType()))
                .contentLength(file.getFileSize())
                .body(resource);
    }

    /**
     * 确保文件名有正确的扩展名
     */
    private String ensureFileExtension(String displayName, String fileType) {
        if (fileType == null || fileType.isEmpty()) {
            return displayName;
        }
        if (displayName.contains(".")) {
            return displayName;
        }
        return displayName + "." + fileType;
    }

    /**
     * 清理过期文件
     */
    private void cleanupOldFiles() {
        long now = System.currentTimeMillis();
        decryptedFilesCache.entrySet().removeIf(entry -> {
            DecryptedFileHolder holder = entry.getValue();
            if (now - holder.lastAccess > MAX_FILE_RETENTION_MINUTES * 60 * 1000) {
                try {
                    Files.deleteIfExists(holder.file.toPath());
                    return true;
                } catch (IOException e) {
                    return false;
                }
            }
            return false;
        });
    }

    /**
     * 解密文件持有者
     */
    private static class DecryptedFileHolder {
        final File file;
        volatile long lastAccess;

        DecryptedFileHolder(File file) {
            this.file = file;
            this.lastAccess = System.currentTimeMillis();
        }

        void updateLastAccess() {
            this.lastAccess = System.currentTimeMillis();
        }
    }
}